#include <thread>
#include <chrono>
#include <boost/asio.hpp>

using namespace std;
using namespace boost::asio;
using namespace boost::log;

#ifndef FORWARDER_FORWARDER_H
#define FORWARDER_FORWARDER_H

class Forwarder
{
private:
    bool stop;
    shared_ptr<thread> fromListenThread;
    shared_ptr<thread> toListenThread;
    shared_ptr<thread> fromWorkerThread;
    shared_ptr<thread> toWorkerThread;
    shared_ptr<ip::tcp::socket> fromSocket;
    shared_ptr<ip::tcp::socket> toSocket;

    string fromIp;
    string toIp;
    int fromPort;
    int toPort;

public:

    Forwarder(int fromPort, int toPort) : stop{false}, fromPort{fromPort}, toPort{toPort}
    {
        auto loop = [&](int port){
            try{
                this->ListenLoop(port);
            }catch (exception ex){
                cout<<ex.what();
            }
        };
        fromListenThread = make_shared<thread>(loop, fromPort);
        toListenThread = make_shared<thread>(loop, fromPort);
    }

    Forwarder(string& fromIp, int fromPort, string& toIp, int toPort) : stop{false}, fromIp(fromIp), fromPort{fromPort}, toIp(toIp), toPort{toPort}
    {
        auto loop = [&](string ip, int port){
            this->ConnectLoop(ip, port);
        };
        fromListenThread = make_shared<thread>(loop, fromIp, fromPort);
        toListenThread = make_shared<thread>(loop, toIp, toPort);
    }

    void Stop()
    {
        stop = true;
        fromListenThread->join();
        toListenThread->join();
    }

private:

    void ListenLoop(int port)
    {
        sources::severity_logger<trivial::severity_level> log;
        BOOST_LOG_NAMED_SCOPE("forwarder");
        BOOST_LOG_SEV(log, trivial::info) << "ListenLoop.start";

        auto loop = [&](shared_ptr<ip::tcp::socket> from, shared_ptr<ip::tcp::socket> to){
            this->WorkerLoop(from, to);
        };

        io_service ioService;
        ip::tcp::acceptor acceptor(ioService, ip::tcp::endpoint(ip::tcp::v4(), port));
        shared_ptr<ip::tcp::socket> s = make_shared<ip::tcp::socket>(ioService);
        acceptor.accept(*s.get());
        while (!stop)
        {
            if(this->fromPort == port)
            {
                fromSocket = s;
                fromWorkerThread = make_shared<thread>(loop, fromSocket, toSocket);
                fromWorkerThread->join();
            }
            else
            {
                toSocket = s;
                toWorkerThread = make_shared<thread>(loop, toSocket, fromSocket);
                toWorkerThread->join();
            }
        }
    }

    void ConnectLoop(string& ip, int port)
    {
        sources::severity_logger<trivial::severity_level> log;
        BOOST_LOG_NAMED_SCOPE("forwarder");
        BOOST_LOG_SEV(log, trivial::info) << "ConnectLoop.start";

        auto loop = [&](shared_ptr<ip::tcp::socket> from, shared_ptr<ip::tcp::socket> to) {
            this->WorkerLoop(from, to);
        };

        auto endpoint = ip::tcp::endpoint(ip::address::from_string(ip), port);
        io_service ioService;
        shared_ptr<ip::tcp::socket> s = make_shared<ip::tcp::socket>(ioService);
        s->open(ip::tcp::v4());
        s->connect(endpoint);
        if(this->fromPort == port)
        {
            fromSocket = s;
            fromWorkerThread = make_shared<thread>(loop, fromSocket, toSocket);
            fromWorkerThread->join();
        }
        else
        {
            toSocket = s;
            toWorkerThread = make_shared<thread>(loop, toSocket, fromSocket);
            toWorkerThread->join();
        }
    }

    void WorkerLoop(shared_ptr<ip::tcp::socket> from, shared_ptr<ip::tcp::socket> to)
    {
        sources::severity_logger<trivial::severity_level> log;
        BOOST_LOG_NAMED_SCOPE("forwarder");
        BOOST_LOG_SEV(log, trivial::info) << "WorkerLoop.start";

        while(!stop)
        {
            if(from == nullptr || !from->is_open() || to == nullptr || !to->is_open())
            {
                this_thread::sleep_for(std::chrono::microseconds(500));
                continue;
            }

            char buf[1024]={0};
            auto length = from->read_some(buffer(buf));
            to->write_some(buffer(buf, length));
        }
    }

};

#endif //FORWARDER_FORWARDER_H
